package br.ufpe.cin.imlm2.util;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import br.ufpe.cin.imlm2.jbeat.soot.common.NoSuchOptionException;

/**
 * Every option field must be INITIALIZED.<br>
 * Every option field must start with the prefix '__' , e.g. __myOptionField <br>
 * Every LIST option field must start (besides de prefix) with one of the
 * following letters depending on its type: <br>
 * # i - for int lists.<br>
 * # s - for String lists.<br>
 * # f - for float lists.<br>
 * 
 * 
 * @author Irineu
 * 
 */
public abstract class Options {

	private static final String FLOAT_PREFIX = "f";

	private static final String STRING_PREFIX = "s";

	private static final String INTEGER_PREFIX = "i";

	protected static final String FIELD_PREFIX = "__";

	protected List<String> booleanAttributes = new ArrayList<String>();
	protected List<String> stringAttributes = new ArrayList<String>();
	protected List<String> stringListAttributes = new ArrayList<String>();
	protected List<String> intAttributes = new ArrayList<String>();
	protected List<String> intListAttributes = new ArrayList<String>();
	protected List<String> floatAttributes = new ArrayList<String>();
	protected List<String> floatListAttributes = new ArrayList<String>();

	protected Options() {
		Field[] attributes = this.getClass().getDeclaredFields();
		for (Field field : attributes) {
			if (field.getName().startsWith(FIELD_PREFIX)) {
				List<String> attributesList = this.switchFieldType(field);
				attributesList.add(field.getName());
			}
		}
	}

	private List<String> switchFieldType(Field f) {
		Class<?> type = f.getType();
		if (type.equals(List.class)) {
			String prefixless = f.getName().replaceFirst(FIELD_PREFIX, "");
			if (prefixless.startsWith(STRING_PREFIX)) {
				return this.stringListAttributes;
			} else if (prefixless.startsWith(INTEGER_PREFIX)) {
				return this.intListAttributes;
			} else if (prefixless.startsWith(FLOAT_PREFIX)) {
				return this.floatListAttributes;
			}

		} else if (type.equals(String.class)) {
			return this.stringAttributes;
		} else if (type.equals(int.class)) {
			return this.intAttributes;
		} else if (type.equals(float.class)) {
			return this.floatAttributes;
		} else if (type.equals(boolean.class)) {
			return this.booleanAttributes;
		}

		throw new Error("Subclass not properly implemented");
	}

	protected void parse(Map<String, String> options)
			throws NoSuchOptionException {
		for (Entry<String, String> entry : options.entrySet()) {
			String key = FIELD_PREFIX + entry.getKey();
			String intListKey = FIELD_PREFIX + INTEGER_PREFIX + entry.getKey();
			String stringListKey = FIELD_PREFIX + STRING_PREFIX
					+ entry.getKey();
			String floatListKey = FIELD_PREFIX + FLOAT_PREFIX + entry.getKey();
			String value = entry.getValue();
			if ((this.booleanAttributes.contains(key))) {
				Boolean bVal = Boolean.parseBoolean(value);
				setField(key, bVal);
			} else if ((this.stringAttributes.contains(key))) {
				setField(key, value);
			} else if ((this.intAttributes.contains(key))) {
				Integer iVal = Integer.parseInt(value);
				setField(key, iVal);
			} else if ((this.floatAttributes.contains(key))) {
				Float fVal = Float.parseFloat(value);
				setField(key, fVal);
			} else if (this.stringListAttributes.contains(stringListKey)) {
				String[] values = value.split(",");
				processList(stringListKey, values);
			} else if (this.intListAttributes.contains(intListKey)) {
				Object[] values = value.split(",");
				for (int i = 0; i < values.length; i++) {
					values[i] = Integer.parseInt((String) values[i]);
				}
				processList(intListKey, values);
			} else if (this.floatListAttributes.contains(floatListKey)) {
				Object[] values = value.split(",");
				for (int i = 0; i < values.length; i++) {
					values[i] = Float.parseFloat((String) values[i]);
				}
				processList(floatListKey, values);
			} else {
				throw new NoSuchOptionException(entry.getKey(),
						"Field with name " + entry.getKey() + " doesn't exist.");
			}

		}
	}

	@SuppressWarnings("unchecked")
	protected void processList(String key, Object[] values) {
		try {
			Field attribute = this.getClass().getDeclaredField(key);
			attribute.setAccessible(true);
			@SuppressWarnings("rawtypes")
			List list = (List) attribute.get(this);
			for (Object str : values) {
				list.add(str);
			}
		} catch (SecurityException e) {
			throw new RuntimeException("Subclass not properly implemented", e);
		} catch (NoSuchFieldException e) {
			throw new RuntimeException("Subclass not properly implemented", e);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException("Subclass not properly implemented", e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Subclass not properly implemented", e);
		}

	}

	protected void setField(String key, Object val) {
		try {
			Field attribute = this.getClass().getDeclaredField(key);
			attribute.setAccessible(true);
			attribute.set(this, val);
		} catch (SecurityException e) {
			throw new RuntimeException("Subclass not properly implemented", e);
		} catch (NoSuchFieldException e) {
			throw new RuntimeException("Subclass not properly implemented", e);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException("Subclass not properly implemented", e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Subclass not properly implemented", e);
		}
	}
}
